package com.devmarvel.creditcardentry.internal;

import com.devmarvel.creditcardentry.animation.CycleInterpolator;
import com.devmarvel.creditcardentry.fields.CreditCardText;
import com.devmarvel.creditcardentry.fields.CreditEntryFieldBase;
import com.devmarvel.creditcardentry.fields.ExpDateText;
import com.devmarvel.creditcardentry.fields.SecurityCodeText;
import com.devmarvel.creditcardentry.fields.ZipCodeText;
import com.devmarvel.creditcardentry.library.CardType;
import com.devmarvel.creditcardentry.library.CardValidCallback;
import com.devmarvel.creditcardentry.library.CreditCard;
import com.devmarvel.creditcardentry.util.Constants;
import com.devmarvel.creditcardentry.util.DisplayUtils;
import com.devmarvel.creditcardentry.util.ResUtil;
import ohos.accessibility.ability.AccessibleAbility;
import ohos.accessibility.ability.SoftKeyBoardController;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.Image;
import ohos.agp.components.InputAttribute;
import ohos.agp.components.ScrollView;
import ohos.agp.components.Text;
import ohos.agp.components.TextField;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CreditCardEntry extends ScrollView implements
        Component.TouchEventListener, CreditCardFieldDelegate {

    private final Context context;
    // null textColor means we want to use the system default color instead of providing our own.
    private final Color textColor;

    private Image cardImage;
    private Image backCardImage;
    private final CreditCardText creditCardText;
    private final ExpDateText expDateText;
    private final SecurityCodeText securityCodeText;
    private final ZipCodeText zipCodeText;

    private Map<CreditEntryFieldBase, CreditEntryFieldBase> nextFocusField = new HashMap<>(4);
    private Map<CreditEntryFieldBase, CreditEntryFieldBase> prevFocusField = new HashMap<>(4);
    private List<CreditEntryFieldBase> includedFields = new ArrayList<>(4);

    private final Text textFourDigits;

    private Text textHelper;

    private boolean showingBack;
    private boolean scrolling = false;
    private boolean animateOnError = true;

    private CardValidCallback onCardValidCallback;

    // custom attributes
    private static final String default_text_colors = "default_text_colors";
    private static final String text_color = "text_color";
    private static final String text_size = "text_size";

    public CreditCardEntry(Context context, boolean includeExp, boolean includeSecurity, boolean includeZip, AttrSet attrs, String style) {
        super(context);

        this.context = context;

        if (attrs != null && !(attrs.getAttr(default_text_colors).isPresent() && attrs.getAttr(default_text_colors).get().getBoolValue())) {
            textColor = attrs.getAttr(text_color).isPresent() ?
                    attrs.getAttr(text_color).get().getColorValue() : Color.BLACK;
        } else {
            textColor = null;
        }
        int width = DisplayUtils.vp2px(context, mContext.getResourceManager().getDeviceCapability().width);

        LayoutConfig params = new LayoutConfig(LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT);
        params.alignment = LayoutAlignment.VERTICAL_CENTER;
        setLayoutConfig(params);
        setEnabled(false);
        this.setTouchEventListener(this);

        DirectionalLayout container = new DirectionalLayout(context);
        container.setId(Constants.Ids.cc_entry_internal);
        container.setLayoutConfig(new LayoutConfig(LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT));
        container.setOrientation(DirectionalLayout.HORIZONTAL);

        creditCardText = new CreditCardText(context, attrs);
        creditCardText.setId(Constants.Ids.cc_card);
        creditCardText.setDelegate(this);
        creditCardText.setWidth(width);
        container.addComponent(creditCardText);
        includedFields.add(creditCardText);
        CreditEntryFieldBase currentField = creditCardText;

        int textSize = (attrs != null && attrs.getAttr(text_size).isPresent()) ?
                attrs.getAttr(text_size).get().getDimensionValue() : DisplayUtils.fp2px(context, 16);

        textFourDigits = new Text(context);
        textFourDigits.setTextSize(textSize);
        if (textColor != null) {
            textFourDigits.setTextColor(textColor);
        }
        container.addComponent(textFourDigits);

        expDateText = new ExpDateText(context, attrs);
        expDateText.setId(Constants.Ids.cc_exp);
        if (includeExp) {
            expDateText.setDelegate(this);
            container.addComponent(expDateText);
            nextFocusField.put(currentField, expDateText);
            prevFocusField.put(expDateText, currentField);
            currentField = expDateText;
            includedFields.add(currentField);
        }

        securityCodeText = new SecurityCodeText(context, attrs);
        securityCodeText.setId(Constants.Ids.cc_ccv);
        if (includeSecurity) {
            securityCodeText.setDelegate(this);
            if (!includeZip) {
                securityCodeText.setInputMethodOption(InputAttribute.ENTER_KEY_TYPE_SEND);
            }

            securityCodeText.setEditorActionListener(new Text.EditorActionListener() {
                @Override
                public boolean onTextEditorAction(int action) {
                    if (InputAttribute.ENTER_KEY_TYPE_SEND == action) {
                        onSecurityCodeValid("");
                        return true;
                    }
                    return false;
                }
            });
            container.addComponent(securityCodeText);
            nextFocusField.put(currentField, securityCodeText);
            prevFocusField.put(securityCodeText, currentField);
            currentField = securityCodeText;
            includedFields.add(currentField);
        }

        zipCodeText = new ZipCodeText(context, attrs);
        zipCodeText.setId(Constants.Ids.cc_zip);
        if (includeZip) {
            zipCodeText.setDelegate(this);
            container.addComponent(zipCodeText);
            zipCodeText.setInputMethodOption(InputAttribute.ENTER_KEY_TYPE_SEND);
            zipCodeText.setEditorActionListener(new Text.EditorActionListener() {
                @Override
                public boolean onTextEditorAction(int action) {
                    if (InputAttribute.ENTER_KEY_TYPE_SEND == action) {
                        onZipCodeValid();
                        return true;
                    }
                    return false;
                }
            });
            nextFocusField.put(currentField, zipCodeText);
            prevFocusField.put(zipCodeText, currentField);
            currentField = zipCodeText;
            includedFields.add(currentField);
        }

        nextFocusField.put(currentField, null);

        this.addComponent(container);

        // when the user taps the last 4 digits of the card, they probably want to edit it
        textFourDigits.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                focusOnField(creditCardText);
            }
        });
    }

    @Override
    public void onCardTypeChange(CardType type) {
        cardImage.setImageElement(ResUtil.getPixelMapDrawable(context, type.frontResource));
        backCardImage.setImageElement(ResUtil.getPixelMapDrawable(context, type.backResource));
        updateCardImage(false);
    }

    @Override
    public void onCreditCardNumberValid(String remainder) {
        nextField(this.creditCardText, remainder);

        updateLast4();
    }

    @Override
    public void onExpirationDateValid(String remainder) {
        nextField(this.expDateText, remainder);
    }

    @Override
    public void onSecurityCodeValid(String remainder) {
        nextField(securityCodeText, remainder);
        updateCardImage(false);
    }

    @Override
    public void onZipCodeValid() {
        nextField(zipCodeText, null);
    }

    @Override
    public void onBadInput(final TextField field) {
        if (animateOnError) {
            CycleInterpolator cycleInterpolator = new CycleInterpolator(2);
            AnimatorProperty shake = field.createAnimatorProperty();
            shake.moveByX(10f).setDuration(500).setCurve(cycleInterpolator);
            shake.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {

                }

                @Override
                public void onStop(Animator animator) {
                    shake.reset();
                }

                @Override
                public void onCancel(Animator animator) {
                    shake.reset();
                }

                @Override
                public void onEnd(Animator animator) {
                    shake.reset();
                }

                @Override
                public void onPause(Animator animator) {

                }

                @Override
                public void onResume(Animator animator) {

                }
            });
            shake.start();
        }

        field.setTextColor(Color.RED);
        final EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
        handler.postTask(new Runnable() {
            @Override
            public void run() {
                if (textColor != null) {
                    field.setTextColor(textColor);
                }
            }
        }, 1000);
    }

    @Override
    public void setFocusChangedListener(FocusChangedListener l) {
        creditCardText.setFocusChangedListener(l);
        expDateText.setFocusChangedListener(l);
        securityCodeText.setFocusChangedListener(l);
        zipCodeText.setFocusChangedListener(l);
    }

    public void focusOnField(final CreditEntryFieldBase field) {
        focusOnField(field, null);
    }

    public void focusOnField(final CreditEntryFieldBase field, String initialFieldValue) {
        field.requestFocus();
        if (!scrolling) {
            scrolling = true;
            scrollToTarget(field instanceof CreditCardText ? 0 : (field.getLeft() + 100), new Runnable() {
                @Override
                public void run() {
                    scrolling = false;
                    // if there was another focus before we were done.. catch up.
                    if (!field.hasFocus()) {
                        Component newFocus = getFocusedChild();
                        if (newFocus instanceof CreditEntryFieldBase) {
                            focusOnField((CreditEntryFieldBase) newFocus);
                        }
                    }
                }
            });
        }

        if (initialFieldValue != null && initialFieldValue.length() > 0) {
            field.formatAndSetText(initialFieldValue);
        }

        if (this.textHelper != null) {
            this.textHelper.setText(field.getHelperText());
        }

        if (field instanceof SecurityCodeText) {
            ((SecurityCodeText) field).setType(creditCardText.getType());
            updateCardImage(true);
        } else {
            updateCardImage(false);
        }
    }

    private Component getFocusedChild() {
        for (int i = 0; i < getChildCount(); i++) {
            Component component = getComponentAt(i);
            if (component.hasFocus()) {
                return component;
            }
        }
        return null;
    }

    private void scrollToTarget(int target, final Runnable after) {
        int scrollX = getScrollValue(Component.AXIS_X);
        if (scrollX == target) {
            if (after != null) after.run();
        } else {
            fluentScrollTo(target, 0);
            if (after != null) after.run();
        }
    }

    @Override
    public void focusOnPreviousField(CreditEntryFieldBase field) {
        CreditEntryFieldBase view = prevFocusField.get(field);
        if (view != null) {
            focusOnField(view);
        }
    }

    public void setCardNumberHint(String hint) {
        creditCardText.setHint(hint);
    }

    /**
     * set the card number will auto focus next field if param is true
     */
    public void setCardNumber(String cardNumber, boolean nextField) {
        setValue(this.creditCardText, cardNumber, nextField);
    }

    public void setCardImageView(Image image) {
        cardImage = image;
    }

    public void setExpDate(String expiration, boolean nextField) {
        setValue(this.expDateText, expiration, nextField);
    }

    public void setSecurityCode(String securityCode, boolean nextField) {
        setValue(this.securityCodeText, securityCode, nextField);
    }

    public void setZipCode(String zip, boolean nextField) {
        setValue(this.zipCodeText, zip, nextField);
    }

    @SuppressWarnings("unused")
    public Image getBackCardImage() {
        return backCardImage;
    }

    public void setBackCardImage(Image backCardImage) {
        this.backCardImage = backCardImage;
    }

    @SuppressWarnings("unused")
    public Text getTextHelper() {
        return textHelper;
    }

    public void setTextHelper(Text textHelper) {
        this.textHelper = textHelper;
    }

    public boolean isCreditCardValid() {
        for (CreditEntryFieldBase includedField : includedFields) {
            if (!includedField.isValid()) return false;
        }
        return true;
    }

    private void setValue(CreditEntryFieldBase fieldToSet, String value, boolean nextField) {
        CreditCardFieldDelegate delegate = null;
        if (!nextField) {
            delegate = fieldToSet.getDelegate();
            // temp delegate that only deals with type.. this sucks.. TODO gut this delegate business
            fieldToSet.setDelegate(getDelegate(delegate));
        }

        fieldToSet.setText(value);

        if (delegate != null) {
            fieldToSet.setDelegate(delegate);
        }
    }

    public void setAnimateOnError(boolean animateOnError) {
        this.animateOnError = animateOnError;
    }

    private CreditCardFieldDelegate getDelegate(final CreditCardFieldDelegate delegate) {
        return new CreditCardFieldDelegate() {
            @Override
            public void onCardTypeChange(CardType type) {
                delegate.onCardTypeChange(type);
            }

            @Override
            public void onCreditCardNumberValid(String remainder) {
                updateLast4();
            }

            @Override
            public void onBadInput(TextField field) {
                delegate.onBadInput(field);
            }

            @Override
            public void onExpirationDateValid(String remainder) {
            }

            @Override
            public void onSecurityCodeValid(String remainder) {
            }

            @Override
            public void onZipCodeValid() {
            }

            @Override
            public void focusOnField(CreditEntryFieldBase field, String initialValue) {
            }

            @Override
            public void focusOnPreviousField(CreditEntryFieldBase field) {
            }
        };
    }

    public void clearAll() {
        creditCardText.setText("");
        expDateText.setText("");
        securityCodeText.setText("");
        zipCodeText.setText("");
        creditCardText.clearFocus();
        expDateText.clearFocus();
        securityCodeText.clearFocus();
        zipCodeText.clearFocus();

        scrollTo(0, 0);
    }

    public CreditCard getCreditCard() {
        return new CreditCard(creditCardText.getText(), expDateText.getText(),
                securityCodeText.getText(), zipCodeText.getText(),
                creditCardText.getType());
    }

    /**
     * request focus for the credit card field
     */
    public void focusCreditCard() {
        focusOnField(creditCardText);
    }

    /**
     * request focus for the expiration field
     */
    public void focusExp() {
        if (includedFields.contains(expDateText)) {
            focusOnField(expDateText);
        }
    }

    /**
     * request focus for the security code field
     */
    public void focusSecurityCode() {
        if (includedFields.contains(securityCodeText)) {
            focusOnField(securityCodeText);
        }
    }

    /**
     * request focus for the zip field (IF it's enabled)
     */
    public void focusZip() {
        if (includedFields.contains(zipCodeText)) {
            focusOnField(zipCodeText);
        }
    }

    private void updateLast4() {
        String number = creditCardText.getText().toString();
        int length = number.length();
        String digits = number.substring(length - 4);
        textFourDigits.setText(digits);
    }

    private void nextField(CreditEntryFieldBase currentField, String initialFieldValue) {
        CreditEntryFieldBase next = nextFocusField.get(currentField);
        if (next == null) {
            entryComplete(currentField);
        } else {
            focusOnField(next, initialFieldValue);
        }
    }

    private void entryComplete(Component clearField) {
        hideKeyboard();
        clearField.clearFocus();
        if (onCardValidCallback != null) onCardValidCallback.cardValid(getCreditCard());
    }

    private void updateCardImage(boolean back) {
        if (showingBack != back) {
            flipCardImage();
        }

        showingBack = back;
    }

    private void flipCardImage() {
    }

    private void hideKeyboard() {
        SoftKeyBoardController ime = new SoftKeyBoardController(AccessibleAbility.SHOW_MODE_AUTO, null);
        ime.setShowMode(AccessibleAbility.SHOW_MODE_HIDE);
    }

    public void setOnCardValidCallback(CardValidCallback onCardValidCallback) {
        this.onCardValidCallback = onCardValidCallback;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        return true;
    }

    /**
     * helper & hint setting
     **/

    public void setCreditCardTextHelper(String text) {
        creditCardText.setHelperText(text);
    }

    public void setCreditCardTextHint(String text) {
        creditCardText.setHint(text);
    }

    public void setExpDateTextHelper(String text) {
        expDateText.setHelperText(text);
    }

    public void setExpDateTextHint(String text) {
        expDateText.setHint(text);
    }

    public void setSecurityCodeTextHelper(String text) {
        securityCodeText.setHelperText(text);
    }

    public void setSecurityCodeTextHint(String text) {
        securityCodeText.setHint(text);
    }

    public void setZipCodeTextHelper(String text) {
        zipCodeText.setHelperText(text);
    }

    public void setZipCodeTextHint(String text) {
        zipCodeText.setHint(text);
    }

}
